//===============================================================================
// Q4Tech Engineering Team
// Mobile Updater Application Block - November 2006
//===============================================================================
// Copyright  Q4Tech Informatica Argentina.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;

namespace Q4Tech.Engineering.Ipc
{
    public class IpcCallServer<TIn, TOut> : IDisposable
    {
        const int chunkSize = 256;
        const int maxMessageCount = 100;
        const String requestQueuePattern = "__{0}";
        const String responseQueuePattern = "__{0}__{1}";
        public delegate TOut CallbackHandler(IpcCallMessage<TIn> message);
        private String publishedName;
        CallbackHandler callbackHandler;
        IPointToPointMessageQueueFactory factory;

        IPointToPointMessageQueue requestQueue;

        public IpcCallServer(IPointToPointMessageQueueFactory factory, String publishedName, CallbackHandler callbackHandler)
        {
            if (publishedName == String.Empty)
                throw new ArgumentException("The published name cannot be an empty string.");

            if (publishedName == null)
                throw new ArgumentNullException("The published name cannot be null.");

            if (callbackHandler == null)
                throw new ArgumentNullException("Callback handler must not be null.");

            if (factory == null)
                throw new ArgumentNullException("Factory argument must not be null.");


            this.publishedName = publishedName;
            this.callbackHandler = callbackHandler;

            this.factory = factory;
            this.requestQueue = factory.Create();
            this.requestQueue.Initialize(String.Format(requestQueuePattern, publishedName), PointToPointMessageQueue.AccessMode.Read, chunkSize, maxMessageCount);

            new Thread(new ThreadStart(Worker)).Start();
        }

        private void Worker()
        {
            const int lapTimeout = 1000;
            const int chunkTimeout = 5000;

            requestQueue.Purge();
            
            while (!disposed)
            {
                try
                {
                    byte[] buffer = requestQueue.Dequeue(lapTimeout);

                    if (buffer == null)
                        continue;

                    IpcCallMessage<TIn> request = new IpcCallMessage<TIn>();

                    while (request.Add(buffer))
                    {
                        buffer = requestQueue.Dequeue(chunkTimeout);

                        if (buffer == null)
                            throw new Exception();
                    }

                    IpcCallMessage<TOut> response = new IpcCallMessage<TOut>(request.ClientId, request.MessageId, callbackHandler(request), chunkSize);

                    IPointToPointMessageQueue responseQueue = factory.Create();
                    responseQueue.Initialize(String.Format(responseQueuePattern, publishedName, response.ClientId), PointToPointMessageQueue.AccessMode.Write, chunkSize, maxMessageCount);

                    buffer = response.GetNextChunk();

                    while (buffer != null)
                    {
                        responseQueue.Enqueue(buffer);

                        buffer = response.GetNextChunk();
                    }
                }
                catch
                {
                }
            }
        }

        #region Dispose pattern
        private bool disposed = false;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                }

                if (requestQueue != null)
                    requestQueue.Dispose();
            }

            disposed = true;
        }

        ~IpcCallServer()
        {
            Dispose(false);
        }
        #endregion
    }
}
